﻿using System;
using System.Collections.Generic;
using System.Web;
using System.Web.Caching;

namespace SlamCms.Caching
{
	public class HttpRuntimeCacheManager : ICacheManager
	{
		public bool Contains(string key, bool allowDirty = true)
		{
			var value = HttpRuntime.Cache.Get(key);
			if (value == null)
				return false;

			if (!allowDirty)
			{
				var metadata = GetCacheEntryMetadata(key);
				if (metadata != null && metadata.IsDirty)
					return false;
			}

			return true;
		}

		public T Get<T>(string key, bool allowDirty = true) where T : class
		{
			var objectValue = HttpRuntime.Cache.Get(key);
			if (objectValue == null)
				return (T)null;

			var metadata = GetCacheEntryMetadata(key);

			if (metadata != null)
			{
				metadata.LastAccessed = DateTime.Now;
				HttpRuntime.Cache[key + "_m"] = metadata;
			}

			if (!allowDirty && metadata != null && metadata.IsDirty)
				return (T)null;

			return objectValue as T;
		}

		public void Save<T>(string key, T instance, TimeSpan? timeout = null, bool sliding = false) where T : class
		{
			if (HttpRuntime.Cache[key] != null)
			{
				HttpRuntime.Cache.Remove(key);
				HttpRuntime.Cache.Remove(key + "_m");
			}

			var absoluteExpiration = timeout.HasValue && !sliding ? DateTime.Now.Add(timeout.Value) : Cache.NoAbsoluteExpiration;
			var slidingExpiration = timeout.HasValue && sliding ? timeout.Value : Cache.NoSlidingExpiration;
			HttpRuntime.Cache.Insert(key, instance, null, absoluteExpiration, slidingExpiration, CacheItemPriority.NotRemovable, RemovedCallback);

			// metadata entry
			var metadata = new CacheEntryMetadata();
			metadata.DateCreated = DateTime.Now;
			metadata.LastAccessed = DateTime.Now;
			metadata.SlidingExpiration = sliding;
			metadata.Duration = timeout;
			metadata.IsDirty = false;
			metadata.Type = instance.GetType();
			metadata.HashCode = instance.GetHashCode();
			HttpRuntime.Cache.Insert(key + "_m", metadata, null, absoluteExpiration, Cache.NoSlidingExpiration, CacheItemPriority.NotRemovable, RemovedCallback);
		}

		public void Remove(string key)
		{
			if (HttpRuntime.Cache[key] == null)
				return;

			// just mark is as dirty
			var metadata = GetCacheEntryMetadata(key);
			if (metadata != null && !metadata.IsDirty)
			{
				metadata.IsDirty = true;
				HttpRuntime.Cache[key + "_m"] = metadata;
			}
		}

		public IEnumerable<string> Keys
		{
			get
			{
				var keys = new List<string>();
				var enumerator = HttpRuntime.Cache.GetEnumerator();
				while (enumerator.MoveNext())
				{
					keys.Add(enumerator.Key.ToString());
				}
				return keys;
			}
		}

		public CacheEntryMetadata GetCacheEntryMetadata(string key)
		{
			var cacheEntryMetadata = HttpRuntime.Cache[key + "_m"] as CacheEntryMetadata;
			return cacheEntryMetadata;
		}

		private void RemovedCallback(string key, object value, CacheItemRemovedReason callbackreason)
		{
		}
	}

	public class CacheEntryMetadata
	{
		public DateTime DateCreated { get; set; }
		public DateTime LastAccessed { get; set; }
		public TimeSpan? Duration { get; set; }
		public bool SlidingExpiration { get; set; }
		public bool IsDirty { get; set; }
		public Type Type { get; set; }
		public int HashCode { get; set; }
	}
}